package icraf.carbon.gui;

import icraf.carbon.core.AbacusContainer;
import icraf.carbon.core.LandCover;
import icraf.carbon.core.LandCoverList;
import icraf.carbon.core.Project;
import icraf.carbon.core.Zone;
import icraf.carbon.core.Zone.DataType;
import icraf.carbon.core.ZoneList;
import icraf.carbon.core.Config.ConfigItem;
import icraf.carbon.gui.MenuFactory.MenuProperty;
import icraf.carbon.gui.ZoneTransformPanel.ZoneTransitionTableModel;
import icraf.common.gui.MenuGenerator.IconModel;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Window;
import java.awt.Dialog.ModalityType;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.ListModel;
import javax.swing.table.TableModel;

public class InputZonePanel extends JPanel {
	public class Listener implements ActionListener, 
			ProjectDockListener, PropertyChangeListener {

		@Override
		public void actionPerformed(ActionEvent e) {
			if (MenuProperty.LIST_ADD.isAction(e)) {
				zoneTableModel.addNew();
				project.getLandCoverList().setUpdated(true);
			} else if (MenuProperty.LIST_REMOVE.isAction(e)) {
				remove();
			} else if (e.getSource().equals(selectTransB)) {
				projectTreePanel.setSelection(project,
						MenuProperty.PROJECT_LCOVER_CHANGE);
			} else if (MenuProperty.LIST_IMPORT_CLIPBOARD.isAction(e)) {
				clipboardPanel.updateClipboardData();
				int opt = JOptionPane.showConfirmDialog(matrixPanel,
						importClipboardPanel, MenuProperty.LIST_IMPORT_CLIPBOARD.getLabel(),
						JOptionPane.YES_NO_OPTION, JOptionPane.PLAIN_MESSAGE);
				if (opt == JOptionPane.YES_OPTION) {
					zoneTableModel.importFromClipboard(clipboardPanel);
				}
			} else if (MenuProperty.GENERAL_TRANSFORM.isAction(e)) {
					zoneTransformPanel.clear();
					JDialog d = zoneTransformPanel.createDialog((Window)matrixPanel.getRootPane().getParent(), ModalityType.APPLICATION_MODAL);
					Point loc = matrixPanel.getRootPane().getLocationOnScreen();
					d.setLocation(loc.x, loc.y);
					d.setVisible(true);
					if (zoneTransformPanel.isTransform()) {
						transformZoneArea();
					}
			}
		}

		@Override
		public void projectDoubleClicked(ProjectPanelEvent e) {
		}

		@Override
		public void projectPopupTriggered(ProjectPanelEvent e,
				Component invoker, int x, int y) {
		}

		@Override
		public void projectSelectionChanged(ProjectPanelEvent e) {
			if (e.properties != MenuProperty.PROJECT_ZONATION && edited) {
				edited = false;
				checkFraction();
			}
		}

		@Override
		public void propertyChange(PropertyChangeEvent evt) {
			if(project == null) return;
			double d = ((Number)areaField.getValue()).doubleValue();
			project.setTotalArea(d);
			zoneTableModel.fireTableDataChanged();
		}

	}
	


	class ZoneTableModel extends MatrixTableModel {
		private static final long serialVersionUID = 8968868974903218229L;
		private String[] headers = {
				MenuProperty.PROJECT_ZONE.getLabel(), 
				MenuProperty.PROJECT_ZONE_AREA.getLabel(),
				MenuProperty.PROJECT_ZONE_FRACTION.getLabel(),
				MenuProperty.INPUT_DESC.getLabel(),
				MenuProperty.PROJECT_ZONE_ELIGIBILITY.getLabel()};
		private NumberRowHeader tableListModel = new NumberRowHeader(this);

		public void addNew() {
			if (project == null)
				return;
			int n = project.getZoneList().getSize();
			project.getZoneList().addNew();
			this.fireTableRowsInserted(n, n);
		}

		public void delete(int[] sel) {
			ArrayList<Zone> ls = new ArrayList<Zone>();
			for (int i : sel) {
				ls.add(project.getZoneList().get(i));
			}
			for (Zone z : ls) {
				project.getZoneList().remove(z);
			}
			fireTableDataChanged();
		}

		@Override
		public Class<?> getColumnClass(int column) {
			if (column == 0 || column == 3)
				return String.class;
			if (column == 4)
				return Boolean.class;
			return Double.class;
		}

		@Override
		public int getColumnCount() {
			return headers.length;
		}

		@Override
		public String getColumnName(int col) {
			return headers[col];
		}

		@Override
		public int getRowCount() {
			if (project == null)
				return 0;
			return project.getNumberOfZone();
		}

		@Override
		public ListModel<Object> getRowHeaderModel() {
			return tableListModel;
		}

		@Override
		public Object getValueAt(int row, int column) {
			Zone zone = project.getZoneList().get(row);
			switch (column) {
			case 0:
				return zone.getLabel();
			case 2:
				return zone.getAreaFraction();
			case 1:
				return zone.getAreaFraction() * project.getTotalArea();
			case 3:
				return zone.getDescription();
			case 4:
				return zone.isEligible();
			}
			return null;
		}

		@Override
		public boolean isCellEditable(int row, int col) {
			return true;
		}

		private void setArea(double area, int row) {
			double areaArr[] = new double[project.getNumberOfZone()];
			double sum = 0;
			for (int i = 0; i < project.getNumberOfZone(); i++) {
				if (i != row) {
					areaArr[i] = project.getZoneList().get(i).getAreaFraction()
							* project.getTotalArea();
				} else {
					areaArr[i] = area;
				}
				sum += areaArr[i];
			}
			project.setTotalArea(sum);
			for (int i = 0; i < project.getNumberOfZone(); i++) {
				project.getZoneList().get(i).setAreaRatio(areaArr[i] / sum);
			}
			setAreaField(project.getTotalArea());
			fireTableDataChanged();
		}

		@Override
		public void setValueAt(Object value, int row, int col) {
			if (value == null)
				return;
			Zone zone = project.getZoneList().get(row);
			if (col == 2) {
				double d = 0;
				if (value instanceof String) {
					String strVal = (String) value;
					strVal = strVal.replaceAll( "[^\\d.,-Ee]", "" );
					try {
						d = formatter.parse(strVal.trim()).doubleValue();
					} catch (Exception e) {
						return;
					}
				} else {
					d = ((Double) value).doubleValue();
				}
				d = Math.abs(d);
				zone.setAreaRatio(d);
			} else if (col == 1) {
				double d = 0;
				if (value instanceof String) {
					String strVal = (String) value;
					strVal = strVal.replaceAll( "[^\\d.,-Ee]", "" );
					try {
						d = formatter.parse(strVal.trim()).doubleValue();
					} catch (Exception e) {
						return;
					}
				} else {
					d = ((Double) value).doubleValue();
				}
				d = Math.abs(d);
				setArea(d, row);
			} else if (col == 0) {
				zone.setLabel(value.toString());
			} else if (col == 3) {
				zone.setDescription(value.toString());
			} else if (col == 4) {
				zone.setEligible((Boolean) value);
			}
			fireTableCellUpdated(row, col);
		}

		public void importFromClipboard(ClipboardPanel clipboardPanel) {
			TableModel table = clipboardPanel.getTableModel();
			if (table.getRowCount() <= 0)
				return;
			ZoneList list = project.getZoneList();
			for (int i = 0; i < list.getSize(); i++) {
				setZoneFields(table, i);
			}

			for (int j = list.getSize(); j < table.getRowCount(); j++) {
				addNew();
				setZoneFields(table, j);
			}
			fireTableDataChanged();
			tableListModel.fireContentChange(0);
		}

		private void setZoneFields(TableModel table, int row) {
			int c = table.getColumnCount();
			for (int i = 0; i < c; i++) {
				setValueAt(table.getValueAt(row, i).toString(), row, i);
			}
		}
	}

	private static final long serialVersionUID = -5844291625322786356L;
	private JFormattedTextField areaField;
	private boolean edited = false;
	private DecimalFormat formatter = (DecimalFormat) NumberFormat
			.getNumberInstance();
	private Listener listener = new Listener();
	private ListToolBar listToolBar;
	private MatrixPanel matrixPanel;
	private Project project;
	private ProjectTreePanel projectTreePanel;
	private JButton selectTransB;
	private ZoneList zoneList;
	private ZoneTableModel zoneTableModel = new ZoneTableModel();
	private ClipboardPanel clipboardPanel = new ClipboardPanel();
	private JPanel importClipboardPanel;
	private NumberFormat areaFormat;
	private ZoneTransformPanel zoneTransformPanel = new ZoneTransformPanel();

	public InputZonePanel() {
		super();

		listener = new Listener();
		JLabel l = new JLabel(MenuProperty.PROJECT_AREA.getLabel() + ":");
		areaFormat = NumberFormat.getNumberInstance();
		areaFormat.setMaximumFractionDigits(32);
		areaField = new JFormattedTextField(areaFormat);
		areaField.setValue(new Double(0));
		areaField.setColumns(20);
		areaField.addPropertyChangeListener(listener);
        
		
		selectTransB = new JButton(MenuProperty.PROJECT_AREA_SET.getLabel());
		selectTransB.addActionListener(listener);

		JPanel p1 = new JPanel();
		p1.add(l);
		p1.add(areaField);
		p1.add(selectTransB);

		JPanel mainPanel = new JPanel(new BorderLayout());
		matrixPanel = new MatrixPanel(zoneTableModel);
		matrixPanel.setRowColor(false);
		mainPanel.setBorder(BorderFactory.createTitledBorder(MenuProperty.PROJECT_ZONE_PARTITION.getLabel()));
		JTable zTab = matrixPanel.getTable();
		zTab.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
		zTab.setIntercellSpacing(new Dimension(5, 1));
		mainPanel.add(matrixPanel);

		listToolBar = new ListToolBar(listener);
		listToolBar.setFloatable(false);
		listToolBar.addSeparator();
		listToolBar.add(MenuFactory.createButtonIcon(MenuProperty.LIST_IMPORT_CLIPBOARD, listener));
		listToolBar.addSeparator(new Dimension(100, 20));
		listToolBar.add(MenuFactory.createButton(MenuProperty.GENERAL_TRANSFORM, listener, true, IconModel.ICON16));
		mainPanel.add(listToolBar, BorderLayout.NORTH);

		this.setLayout(new BorderLayout());
		this.setBorder(BorderFactory.createEmptyBorder(5, 20, 20, 20));
		this.add(p1, BorderLayout.NORTH);
		this.add(mainPanel, BorderLayout.CENTER);

		String[] h = new String[zoneTableModel.getColumnCount()];
		for (int i = 0; i < h.length; i++) {
			h[i] = zoneTableModel.getColumnName(i);
		}
		clipboardPanel.setFields(h);
		clipboardPanel.setPreferredSize(new Dimension(300, 300));
		importClipboardPanel = new JPanel();
		importClipboardPanel.setLayout(new BorderLayout(5, 5));
		importClipboardPanel.add(new JLabel(MenuProperty.LIST_IMPORT_CLIPBOARD.getAccessibleDescription() ),
				BorderLayout.NORTH);
		importClipboardPanel.add(clipboardPanel);

		zoneTransformPanel.setPreferredSize(new Dimension(600, 400));
	}

	private HashMap<Zone, HashMap<LandCover, Double>> transformFactor = new HashMap<Zone, HashMap<LandCover, Double>>();
	
	public void transformZoneArea() {
		int opt = JOptionPane
				.showConfirmDialog(
						this,
						MenuProperty.GENERAL_TRANSFORM_WARNING.getAccessibleDescription(),
						MenuProperty.GENERAL_TRANSFORM_WARNING.getLabel(),	
						JOptionPane.YES_NO_OPTION,
						JOptionPane.WARNING_MESSAGE);
		if (opt != JOptionPane.YES_OPTION)
			return;
		// TODO Auto-generated method stub
		transformFactor.clear();
		
		ZoneTransitionTableModel ztm = zoneTransformPanel.getZoneTransformMatrix();
		double newZoneFrac[] = new double[project.getNumberOfZone()];
		for (int j = 0; j < project.getNumberOfZone(); j++) {
			for (int i = 0; i < project.getNumberOfZone(); i++) {
				newZoneFrac[j] += (Double)ztm.getValueAt(i, j);
			}
			Zone zone = project.getZoneList().get(j);
			zone.setAreaRatio(newZoneFrac[j]);
			//zone.setValue(d, lc1, lc2, t, attributes)
			for (int i = 0; i < project.getNumberOfZone(); i++) {
				
				if(i==j) continue;
				Zone zoneLC = project.getZoneList().get(i);
				LandCoverList lcl = project.getLandCoverList();
				double sumCol[] = new double[lcl.size()];
				for(LandCover lc2: lcl) {
					for(LandCover lc1: lcl) {
						sumCol[lcl.indexOf(lc2)] += zoneLC.getValue(lc1, lc2, DataType.LANDCOVER_CHANGE, null);
					}
				}
				double ft = (Double)ztm.getValueAt(i, j)/newZoneFrac[j]; //transform fraction (0-1) per column zone
				for(LandCover lc2: lcl) {
					double vt = getValueTranform(zone, lc2);
					setValueTranform(vt+sumCol[lcl.indexOf(lc2)]*ft, zone, lc2);
				}
			}
		}
		
		for (int i = 0; i < project.getNumberOfZone(); i++) {
			Zone zone = project.getZoneList().get(i);
			LandCoverList lcl = project.getLandCoverList();
			//System.out.println("*** " +zone.getLabel());
			for(LandCover lc1: lcl) {
				//System.out.println(lc1.getLabel()+ " = " +getValueTranform(zone, lc1));
				for(LandCover lc2: lcl) {
					double v = zone.getValue(lc1, lc2, DataType.LANDCOVER_CHANGE, null);
					double d = v*getValueTranform(zone, lc1);
					if(d==0) d= v;
					zone.setValue(d,lc1,lc2, DataType.LANDCOVER_CHANGE, null);
				}
			}
			zone.normalizeValue(DataType.LANDCOVER_CHANGE, null);
		}
		zoneTableModel.fireTableDataChanged();

	}
	
	public void setValueTranform(double d, Zone z1, LandCover z2) {
		HashMap<Zone, HashMap<LandCover, Double>> data = transformFactor;

		HashMap<LandCover, Double> lc1List = data.get(z1);
		if (lc1List == null) {
			lc1List = new HashMap<LandCover, Double>();
		}
		lc1List.put(z2, d);
		data.put(z1, lc1List);
	}

	public double getValueTranform(Zone z1, LandCover z2) {
		HashMap<Zone, HashMap<LandCover, Double>> data = transformFactor;
		if ((z1 == null) || (z2 == null)) {
			return 0d;
		}
		HashMap<LandCover, Double> lc1List = data.get(z1);
		if (lc1List == null)
			return 0d;
		if (lc1List.get(z2) == null)
			return 0d;
		double d = lc1List.get(z2);
		return d;
	}

	private void checkFraction() {

		double areaArr[] = new double[project.getNumberOfZone()];
		double sumFraction = 0;
		double sumArea = 0;
		for (int i = 0; i < project.getNumberOfZone(); i++) {
			double frac = project.getZoneList().get(i).getAreaFraction();
			sumFraction += frac;
			areaArr[i] = frac * project.getTotalArea();
			sumArea += areaArr[i];
		}
		//double precision = 10000000;
		double precision = ConfigItem.PRECISION_MAX.getValueDouble();
		double precision10 = (float)Math.pow(10, precision);
		sumFraction = Math.round(sumFraction * precision10) / precision10;
		if (sumFraction == 1)
			return;
		int opt = JOptionPane
				.showConfirmDialog(
						this,
						MenuProperty.PROJECT_ZONE_FRACTION_ERROR.getAccessibleDescription(),
						MenuProperty.PROJECT_ZONE_FRACTION_ERROR.getLabel(),						
						JOptionPane.YES_NO_OPTION,
						JOptionPane.WARNING_MESSAGE);
		if (opt != JOptionPane.YES_OPTION) {
			projectTreePanel.setSelection(project, MenuProperty.PROJECT_ZONATION);
			return;
		}
		for (int i = 0; i < project.getNumberOfZone(); i++) {
			project.getZoneList().get(i).setAreaRatio(areaArr[i] / sumArea);
		}
		project.setTotalArea(sumArea);
		setAreaField(project.getTotalArea());
	}

	public AbacusContainer getProject() {
		return project;
	}

	private void remove() {

		int sel[] = matrixPanel.getTable().getSelectedRows();
		if (sel.length <= 0)
			return;
		if (project.getNumberOfZone() == 1) {
			JOptionPane.showMessageDialog(this,
					MenuProperty.PROJECT_ZONE_ERROR.getAccessibleDescription(),
					MenuProperty.PROJECT_ZONE_ERROR.getLabel(),		
					JOptionPane.INFORMATION_MESSAGE);
			return;
		}
		int opt = JOptionPane
				.showConfirmDialog(
						this,
						MenuProperty.PROJECT_ZONE_DELETE.getAccessibleDescription(),
						MenuProperty.PROJECT_ZONE_DELETE.getLabel(),	
						JOptionPane.YES_NO_OPTION,
						JOptionPane.WARNING_MESSAGE);
		if (opt != JOptionPane.YES_OPTION)
			return;
		zoneTableModel.delete(sel);
	}

	private void setAreaField(double d) {
		areaField.setText(formatter.format(d));
	}

	public void setProject(Project project) {
		this.project = project;
		zoneList = project.getZoneList();
		NumberRowHeader m = (NumberRowHeader) zoneTableModel
				.getRowHeaderModel();
		m.fireContentChange(zoneList.getSize());
		zoneTableModel.fireTableStructureChanged();
		matrixPanel.getTable().getColumnModel().getColumn(3)
				.setPreferredWidth(40);
		setAreaField(project.getTotalArea());
		edited = true;
		
		zoneTransformPanel.setProject(project);
	}

	public void setProjectTreePanel(ProjectTreePanel p) {
		this.projectTreePanel = p;
		p.addProjectDockListener(listener);
	}
}
